<?php

/**
 * @author: yanguojain<1007960476@qq.com>
 * 简单 es query builder
 * 直接调用es接口查询类
 */


/*
 * 常用用法: $esServer->setIndex(EsSearch::ES_INDEX_CASE_EN_RESUME)
            ->setWhere($where)
            ->setGroupBy($condition)
            ->setSize(0)
            ->search();

  $where = [
            EsSearch::EQUAL => $searchParams,   //不等于集合
            EsSearch::NOTEQUAL => ['flowat' => '01']  //等于集合
        ]

 $condition = ['groupByParam' => $group];  // groupByParam => 要groupby 字段 也可以使用二维数组进行多个字段分组

*
 *
 *
 *
 */


namespace App\Service;

use EchoPHP\Facades\Log;
use Elasticsearch\ClientBuilder;
use Exception;

class EsSearch {

    /**
     * 聚合方法
     */
    const COUNT = 'count';
    const AVERAGE = 'average';
    const MAX = 'max';
    const MIN = 'min';
    const SUM = 'sum';

    //es排序 升序降序
    const ES_DESC = 'desc';
    const ES_ASC = 'asc';

    //索引范围符号
    //大于或等于
    const FILE_RANGE_GTE = 'gte';
    //小于或等于
    const FILE_RANGE_LTE = 'lte';
    //大于
    const FILE_RANGE_GT = 'gt';
    //小于
    const FILE_RANGE_LT = 'lt';


    /**
     * 查询过滤内容
     */
    const FILTER_PATH_AGGS = 'aggregations';


    /**
     * 操作符
     */
    const IN = 'in';
    const NOTIN = 'not in';
    const EQUAL = "=";
    const NOTEQUAL = '!=';
    const RANGE = "range";

    const PREFIX = 'prefix';

    // 索引管理
    //caseresume表
    const ES_INDEX_CASE_EN_RESUME = 'rpo_caseresume';
    //用户信息索引
    const ES_INDEX_RPO = 'rpo';
    //flow_tag表
    const ES_INDEX_RPO_RELATE_CASE_CASEFLOW = 'rpo_caseresumeflowtag';

    //rpo_en_caseresume索引字段字典
    private $rpoEnCaseResumeFilters = [
        'caseresumeid', 'resumeid', 'projectid', 'caseid', 'createdate', 'updatedate', 'flowat_flowatstatus', 'flowat',
        'flowatstatus', 'subid', 'status', 'timecontrol', 'timecontrol_tag', 'waitflow', 'tagadddate', 'tagadderid',
        'tagaddername', 'creator', 'updator', 'addbycustomer', 'keycode', 'tagadderteamcode'
    ];

    //rpo_relate_case_caseflow索引字段字典
    private $rpoRelateCaseFlowFilters = [
        'caseresumeflowtagid', 'resumeid', 'caseid', 'projectid', 'subid', 'creator', 'createdate', 'updator',
        'updatedate', 'flowat_flowatstatus', 'flowat', 'flowatstatus', 'status', 'keycode', 'tagadderteamcode'
    ];

    //rpo用戶索引字典
    private $rpoFilters = [
        'area', 'birthday', 'cname', 'company', 'currsalaryfrom', 'currsalaryto', 'degree', 'email', 'expectarea',
        'expsalaryfrom', 'expsalaryto', 'funtype', 'gender', 'lastupdate', 'major', 'mobilephone', 'position', 'resumeid',
        'school', 'workindustry', 'workyear'
    ];

    /**
     * 查询的字段
     * @var string
     */
    private $fields;

    /**
     * 索引
     * @var array
     */
    private $index;

    /**

     * 查询过滤内容
     * @var
     */
    private $filterPath;

    /**
     * 查询数据量
     * @var int
     */
    private $size = 10000;

    /**
     * 查询方式
     * @var string
     */
    private $function = 'bool';

    /**
     * @var array
     */
    private $must = [];

    /**
     * @var array
     */
    private $mustNot = [];

    /**
     * @var array
     */
    private $should = [];

    /**
     * @var array
     */
    private $filter = [];

    /**
     *
     * @var array
     */
    private $aggregations = [];

    /**
     * @var
     */
    private $groupName = [];

    /**
     * @var array
     */
    private $order = [];


    /**
     * 对象实例
     * @var
     */
    private static $_instance;

    /**
     * 上次执行查询结果
     * @var string
     */
    private $lastQuery = '';

    protected function __construct() {
        $options = config('elasticsearch', []);
        $this->client = ClientBuilder::create()
            ->setConnectionPool('\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool', []) //连接池设置 若es节点挂了则不请求
            ->setSelector('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector')    //设置轮训请求es节点
            ->setHosts($options)
            ->build();
    }

    /**

     * 获取对象实例
     * @return $this
     */
    public static function getInstance() {
        if (!static::$_instance) {
            static::$_instance = new static();
        }
        static::$_instance->init();
        return static::$_instance;
    }

    /**
     * 数据初始化
     * @return $this
     */
    public function init() {
        $this->fields = null;
        $this->index = null;
        $this->filterPath = null;
        $this->size = 10000;
        $this->function = 'bool';
        $this->must = [];
        $this->mustNot = [];
        $this->should = [];
        $this->filter = [];
        $this->aggregations = [];
        $this->groupName = [];
        $this->order = [];

        return $this;
    }

    /**
     * 设置索引
     * @param $index
     * @return $this
     */
    public function setIndex($index): self {
        $this->index = $index;
        return $this;
    }

    /**
     * 设置查询过滤内容
     * @param $filterPath
     * @return $this
     */
    public function setFilterPath($filterPath): self {
        $this->filterPath = $filterPath;
        return $this;
    }

    /**
     * 设置查询量
     * @param $size
     * @return $this
     */
    public function setSize($size): self {
        $this->size = $size;
        return $this;
    }

    /**
     * 设置查询字段 不设置则为全部
     * @param $filed
     * @return EsSearch
     */
    public function setFields($filed): self {
        $this->fields = explode(',', $filed);
        return $this;
    }

    /**
     * 设置查询方式 默认查询是 bool
     * @param $function
     * @return $this
     */
    public function setSelectFunction($function): self {
        $this->function = $function;
        return $this;
    }

    /**
     * 设置查询条件
     * @param array $where
     * @return $this
     */
    public function setWhere(array $where): self {
        switch ($this->function) {
            case "bool":
                foreach ($where as $key => $value) {
                    foreach ($value as $i => $item) {
                        $this->where($i, $item, $key);
                    }
                }
                break;
        }
        return $this;
    }

    /**
     * 查询条件解析
     * @param $field
     * @param $values
     * @param $operator
     * @return $this
     * @throws Exception
     */
    public function where($field, $values, $operator): self {
        $operator = strtolower($operator);
        $status = $this->filterParam($field, $values);
        if (!$status) {
            return $this;
        }
        switch ($operator) {
            //in条件
            case  self::IN:
                if (!is_array($values)) {
                    $values = [$values];
                }
                $values = array_values($values);
                $this->must[] = ['terms' => [$field => $values]];
                break;
            //notin条件
            case  self::NOTIN:
                if (!is_array($values))
                    $values = [$values];
                $this->mustNot[] = ['terms' => [$field => array_values($values)]];
                break;
            //不等于
            case  self::NOTEQUAL:
                if (!is_array($values)) {
                    $this->mustNot[] = ['term' => [$field => $values]];
                } else {
                    $this->mustNot[] = ['terms' => [$field => $values]];
                }
                break;
            //等于
            case  self::EQUAL:
                if (is_array($values)) {
                    $this->must[] = ['terms' => [$field => $values]];
                } else {
                    $this->must[] = ['term' => [$field => $values]];
                }
                break;
            //范围查询
            case  self::RANGE:
                $this->filter[] = ['range' => [$field => $values]];
                break;
            //前缀搜索
            case self::PREFIX:
                $this->must[] = ['prefix' => [$field => $values]];
                break;
            //后续可添加
        }
        return $this;
    }

    /**
     * 使用group by 聚合写法
     * @param $groupContent 语法规则，形同sql中的group by语法， group by field [desc|asc] [top:N][,field [desc|asc] [top:N]]
     * field 按什么字段聚合， 升降序，默认升序， 查询前N条数据（不同于sql，这个为特殊定义），默认查询前100条
     * @example group by tagadderteamcode desc,flowat_flowatstatus desc top:200
     */
    public function setGroupBySql($groupContent) {
        $this->aggregations = [];
        $groupContent = strtolower(trim($groupContent));
        if (strpos($groupContent, 'group by') !== 0) {
            return $this;
        }
        $groupContent = substr($groupContent, 9);
        $groupArr = explode(',', $groupContent);

        $conditions = &$this->aggregations;
        foreach ($groupArr as $item) {
            preg_match_all('/\S+/', $item, $matches);
            if (empty($matches[0])) {
                continue;
            }
            $groupField = $matches[0][0];
            $groupSort = isset($matches[0][1]) && $matches[0][1] == 'desc' ? 'desc' : 'asc';
            if (isset($matches[0][2])) {
                $groupSize = intval(substr($matches[0][2], 4));
            }
            $groupSize = isset($groupSize) && $groupSize > 0 ? $groupSize : 100;

            $aggregations = [
                'aggs' => [
                    'g_by_' . $groupField => [
                        //使用聚合捅分组
                        'terms' => [
                            'field' => $groupField,
                            'size' => $groupSize,
                            'order' => ['_key' => $groupSort]
                        ]
                    ]
                ]
            ];
            $conditions = array_merge($conditions, $aggregations);
            $conditions = &$conditions['aggs']['g_by_' . $groupField];
        }
        $this->aggregations = $this->aggregations['aggs'];
        return $this;
    }

    /**
     * 设置分组
     * @param array $condition
     * @return $this
     */
    public function setGroupBy(array $condition): self {
        if (is_array($condition) && !empty($condition)) {
            //一位数组时候
            if (count($condition) == count($condition, 1)) {
                $groupByParam = $condition['groupByParam'] ?? '';
                $mixMethod = $condition['mixMethod'] ?? 'terms';
                //分组查询
                if (!empty($groupByParam)) {
                    $groupName = 'group_by_' . $groupByParam;
                    if (in_array($groupName, $this->groupName)) {
                        return $this;
                    }
                    $this->groupName[] = $groupName;
                    $this->aggregations[$groupName] = [
                        $mixMethod => ['field' => $groupByParam]
                    ];
                }
            } else {
                foreach ($condition as $value) {
                    //获取分组字段
                    $groupByParam = $value['groupByParam'] ?? '';
                    $mixMethod = $value['mixMethod'] ?? 'terms';
                    //分组查询
                    if (!empty($groupByParam)) {
                        $groupName = 'group_by_' . $groupByParam;
                        if (in_array($groupName, $this->groupName)) {
                            continue;
                        }
                        $this->groupName[] = $groupName;
                        $this->aggregations[$groupName] = [
                            $mixMethod => ['field' => $groupByParam]
                        ];
                    }
                }
            }
        }
        return $this;
    }

    /**
     * 设置排序
     * @param array $orderParam
     * @return $this
     * @example
     * [
     *  'updatedate' => 'desc',
     *  'createdate' => 'asc'
     * ]
     */
    public function setOrder(array $orderParams): self {
        $this->order = [];
        foreach ($orderParams as $field => $sortType) {
            $this->order[] = [
                $field => $sortType == self::ES_DESC ? self::ES_DESC : self::ES_ASC
            ];
        }
        return $this;
    }

    /**
     * 过滤空值和索引字段
     * @param $param
     * @param $value
     * @return bool
     * @throws Exception
     */
    public function filterParam($param, $value): bool {
        if (empty($this->index)) {
            throw new Exception('清先设置索引起！');
        }
        switch ($this->index) {
            case self::ES_INDEX_CASE_EN_RESUME:
                $filterList = $this->rpoEnCaseResumeFilters;
                break;
            case self::ES_INDEX_RPO_RELATE_CASE_CASEFLOW:
                $filterList = $this->rpoRelateCaseFlowFilters;
                break;
            case self::ES_INDEX_RPO:
                $filterList = $this->rpoFilters;
                break;
        }
        //参数过滤
        if (!in_array($param, $filterList) || is_null($value)) {
            return false;
        }
        return true;
    }

    /**
     * 获取es查询语句
     * @return array
     * @throws Exception
     */
    public function getQuery(): array {
        if (empty($this->index)) {
            throw new Exception("索引不能为空！");
        }
        $params = [
            'index' => $this->index,
            'body' => [
                "query" => [
                    $this->function => []
                ],
                'size' => $this->size
            ],

        ];
        if ($this->filterPath) {
            $params['filter_path'] = $this->filterPath;
        }
        if (!empty($this->fields)) {
            $params['body']['_source'] = $this->fields;
        }

        if (!empty($this->must)) {
            $params['body']['query'][$this->function]['must'] = $this->must;
        }

        if (!empty($this->mustNot)) {
            $params['body']['query'][$this->function]['must_not'] = $this->mustNot;
        }

        if (!empty($this->should)) {
            $params['body']['query'][$this->function]['should'] = $this->should;
        }

        if (!empty($this->filter)) {
            $params['body']['query'][$this->function]['filter'] = $this->filter;
        }

        if (!empty($this->aggregations)) {
            $params['body']['aggs'] = $this->aggregations;
        }

        if (!empty($this->order)) {
            $params['body']['sort'] = $this->order;
        }

        return $params;
    }

    /**
     * 解析es返回的数据
     * @param $data
     * @return array
     */
    public function parseEsData($data): array {
        $total = $data['hits']['total']['value'] ?? 0;
        $parseData = $data['hits']['hits'] ?? [];
        $res = [];
        if (!empty($parseData)) {
            foreach ($parseData as $item) {
                $res[] = $item['_source'];
            }
        }
        //解析分组排序数据
        $aggregations = $data['aggregations'] ?? [];
//        if (isset($data['aggregations']) && !empty($data['aggregations'])) {
//            $aggregations = $this->parseAggsData($data['aggregations']);
//        }
        return array('total' => $total, 'data' => $res, 'aggregations' => $aggregations);
    }

    /**
     * 解析统计类聚合数据结果
     * @param array $aggsData
     * @return array
     */
    public function parseCountAgg(array $aggsData) {
        $data = [];

        foreach ($aggsData as $groupName => $groupData) {
            foreach ($groupData['buckets'] as $bucketItem) {

                $key = $bucketItem['key'];
                $count = $bucketItem['doc_count'];
                unset($bucketItem['key'], $bucketItem['doc_count']);

                if (empty($bucketItem)) {
                    $data[$key] = $count;
                } else {
                    $data[$key] = $this->parseCountAgg($bucketItem);
                }
            }
        }
        return $data;
    }

    /**
     * 获取最后一次查询结果
     * @return string
     */
    public function getLastQuery() {
        return $this->lastQuery;
    }


    /**
     * 查询入口
     * @return array
     */
    public function search(): array {
        try {
            $searchParam = $this->getQuery();
            $this->lastQuery = json_encode($searchParam['body']);
            $timeStart = $this->msectime();


            $content = $this->client->search($searchParam);
            $timeEnd = $this->msectime();
            $searchData = $this->parseEsData($content);
            //debug模式下，记录接口调用日志
            if (env('APP_DEBUG')) {
                Log::info('ES查询索引请求数据信息', [
                    'requestUrl' => '/' . $this->index . '/_search',
                    'body' => json_encode($searchParam['body']),
                    'time' => $timeStart - $timeEnd  //测试查询消耗时间
                ]);
            }

            return [
                'data' => $searchData['data'],
                'totalnum' => $searchData['total'],
                'aggregations' => $searchData['aggregations'],
                'eserror' => 0,
                'lastquery' => env('APP_DEBUG') ? $this->getLastQuery() : '',
            ];
        } catch (Exception $e) {
            Log::error("ES请求出错：" . $e->getMessage(), [
                'requestUrl' => '/' . $this->index . '/_search',
                'body' => json_encode($searchParam['body'])
            ]);

            return [
                'data' => [],
                'totalnum' => 0,
                'aggregations' => [],
                'eserror' => 1,
                'lastquery' => env('APP_DEBUG') ? $this->getLastQuery() : '',
            ];
        }
    }

    //返回当前的毫秒时间戳
    function msectime() {
        list($msec, $sec) = explode(' ', microtime());
        $msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
        return $msectime;
    }


}
